var create = function (el, text, typeNumber, errorCorrectionLevel) {
  var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
  var _modules = null;
  var _moduleCount = typeNumber * 4 + 17;
  var _dataCache = null;
  var maskPattern = 0;

  // 1、二维码规格
  _modules = moduleSize(_moduleCount)
  // 2、定位图案
  setupPositionProbePattern(0, 0);
  setupPositionProbePattern(_moduleCount - 7, 0);
  setupPositionProbePattern(0, _moduleCount - 7);
  // 3、对齐图案
  setupPositionAdjustPattern(typeNumber);
  // 4、时序图案
  setupTimingPattern();
  // 5、格式信息
  setupTypeInfo(_errorCorrectionLevel, maskPattern);
  // 6、版本信息
  if (typeNumber >= 7) {
    setupTypeNumber(typeNumber);
  }
  // 7、格式化数据
  _dataCache = createData(typeNumber, _errorCorrectionLevel);
  // debugger
  // var str = ''
  // for (let i = 0; i < 44; i++) {
  //   str += _dataCache[i].toString(2).padStart(8, '0')
  // }
  // console.log(str)

  // 8、蒙版
  maskData(_dataCache, maskPattern);
  // 9、绘制
  draw(el, _modules)

  /**
   * 1、初始化二维码尺寸
   * @param {Number} moduleCount
   * @returns {Array}
   */
  function moduleSize(moduleCount) {
    var modules = new Array(moduleCount);
    for (var row = 0; row < moduleCount; row += 1) {
      modules[row] = new Array(moduleCount);
      for (var col = 0; col < moduleCount; col += 1) {
        modules[row][col] = null;
      }
    }
    return modules;
  }

  /**
   * 2、绘制定位图案 
   * @param {Number} row 列
   * @param {Number} col 行
   */
  function setupPositionProbePattern(row, col) {
    // 以row、col为坐标原点；r为横坐标，c为负向纵坐标
    for (var r = -1; r <= 7; r += 1) {
      if (row + r <= -1 || _moduleCount <= row + r) continue;
      for (var c = -1; c <= 7; c += 1) {
        if (col + c <= -1 || _moduleCount <= col + c) continue;
        // 横向 竖向 中心
        if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
          _modules[row + r][col + c] = true;
        } else {
          _modules[row + r][col + c] = false;
        }
      }
    }
  };

  /**
   * 3、对齐图案 
   * @param {Number} typeNumber 级别
   */
  function setupPositionAdjustPattern(typeNumber) {
    // 对齐图案可放置坐标 注：值为中心点
    var pos = PATTERN_POSITION_TABLE[typeNumber - 1];
    for (var i = 0; i < pos.length; i += 1) {
      for (var j = 0; j < pos.length; j += 1) {
        var row = pos[i];
        var col = pos[j];
        if (_modules[row][col] != null) {
          continue;
        }
        for (var r = -2; r <= 2; r += 1) {
          for (var c = -2; c <= 2; c += 1) {
            if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) {
              _modules[row + r][col + c] = true;
            } else {
              _modules[row + r][col + c] = false;
            }
          }
        }
      }
    }
  };

  /**
   * 4、时序图案 
   */
  function setupTimingPattern() {
    // 纵向
    for (var r = 8; r < _moduleCount - 8; r += 1) {
      if (_modules[r][6] != null) {
        continue;
      }
      _modules[r][6] = (r % 2 == 0);
    }
    // 横向
    for (var c = 8; c < _moduleCount - 8; c += 1) {
      if (_modules[6][c] != null) {
        continue;
      }
      _modules[6][c] = (c % 2 == 0);
    }
  };

  /**
   * 5、格式信息
   * @param {Number} _errorCorrectionLevel 纠错级别
   * @param {Number} maskPattern 蒙版索引
  */
  function setupTypeInfo(_errorCorrectionLevel, maskPattern) {
    // 纠错级别左移动3位 与 蒙版 或运算 [x,x,y,y,y]
    var data = (_errorCorrectionLevel << 3) | maskPattern;
    var bits = QRUtil.getBCHTypeInfo(data);

    // 纵向
    for (var i = 0; i < 15; i += 1) {
      var mod = ((bits >> i) & 1) == 1;
      if (i < 6) {
        _modules[i][8] = mod;
      } else if (i < 8) {
        _modules[i + 1][8] = mod;
      } else {
        _modules[_moduleCount - 15 + i][8] = mod;
      }
    }

    // 横向
    for (var i = 0; i < 15; i += 1) {
      var mod = ((bits >> i) & 1) == 1;
      if (i < 8) {
        _modules[8][_moduleCount - i - 1] = mod;
      } else if (i < 9) {
        _modules[8][15 - i - 1 + 1] = mod;
      } else {
        _modules[8][15 - i - 1] = mod;
      }
    }
    _modules[_moduleCount - 8][8] = true;
  };

  /**
   * 6、版本信息 
   * @param {Number} typeNumber 级别
   */
  function setupTypeNumber(typeNumber) {
    var bits = QRUtil.getBCHTypeNumber(typeNumber);
    for (var i = 0; i < 18; i += 1) {
      var mod = ((bits >> i) & 1) == 1;
      _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
    }

    for (var i = 0; i < 18; i += 1) {
      var mod = ((bits >> i) & 1) == 1;
      _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
    }
  };

  /**
   * 7、格式化数据 
   * @param {Number} typeNumber 级别
   * @param {Number} _errorCorrectionLevel 纠错级别
   */
  function createData(typeNumber, errorCorrectionLevel) {
    var rsBlocks = QRUtil.getRSBlocks(typeNumber, errorCorrectionLevel);
    var buffer = QRUtil.qrBitBuffer();
    // UTF-8 转 二进制数组
    var data = QRUtil.qr8BitByte(text);
    // 填充 0100 占4位
    buffer.put(data.getMode(), 4);
    // 填充 数据长度 占位 8位 或 16位
    buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber));
    // 填充数据
    data.write(buffer);

    // calc num max data.
    var totalDataCount = 0;
    for (var i = 0; i < rsBlocks.length; i += 1) {
      totalDataCount += rsBlocks[i].dataCount;
    }

    if (buffer.getLengthInBits() > totalDataCount * 8) {
      throw 'code length overflow. ('
      + buffer.getLengthInBits()
      + '>'
      + totalDataCount * 8
      + ')';
    }

    // end code
    if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
      buffer.put(0, 4);
    }

    // padding
    while (buffer.getLengthInBits() % 8 != 0) {
      buffer.putBit(false);
    }

    // padding
    while (true) {
      if (buffer.getLengthInBits() >= totalDataCount * 8) {
        break;
      }
      buffer.put(PAD0, 8);
      if (buffer.getLengthInBits() >= totalDataCount * 8) {
        break;
      }
      buffer.put(PAD1, 8);
    }
    return QRUtil.createBytes(buffer, rsBlocks);
  };

  /**
   * 8、蒙版 
   * @param {Number} typeNumber 级别
   */
  function maskData(data, maskPattern) {
    var inc = -1;
    var row = _moduleCount - 1;
    var bitIndex = 7;
    var byteIndex = 0;
    var maskFunc = QRUtil.getMaskFunction(maskPattern);

    for (var col = _moduleCount - 1; col > 0; col -= 2) {
      if (col == 6) col -= 1;
      while (true) {
        for (var c = 0; c < 2; c += 1) {
          if (_modules[row][col - c] == null) {
            var dark = false;
            if (byteIndex < data.length) {
              dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
            }
            var mask = maskFunc(row, col - c);
            // 蒙层
            if (mask) {
              dark = !dark;
            }
            _modules[row][col - c] = dark;
            bitIndex -= 1;
            if (bitIndex == -1) {
              byteIndex += 1;
              bitIndex = 7;
            }
          }
        }
        row += inc;
        if (row < 0 || _moduleCount <= row) {
          row -= inc;
          inc = -inc;
          break;
        }
      }
    }
  };

  /**
   * 9、绘制 
   * @param {String} el 元素ID
   * @param {Array} data 数据
   */
  function draw(el, data) {
    const cellSize = 3;
    const margin = 4;
    const canvas = document.getElementById(el)
    canvas.style.border = '1px solid #000';
    canvas.width = cellSize * data.length + margin * 2
    canvas.height = cellSize * data.length + margin * 2
    const qr = canvas.getContext("2d");
    qr.fillStyle = "#000";
    for (let row = 0; row < data.length; row++) {
      for (let col = 0; col < data[row].length; col++) {
        const i = data[row][col];
        if (i) {
          qr.fillRect(row * cellSize + margin, col * cellSize + margin, cellSize, cellSize)
        }
      }
    }
  }
}

const QRUtil = {
  // 5、格式信息
  getBCHTypeInfo: function (data) {
    // 左移10位
    var d = data << 10;
    // 若G15二进制位数(11) 小于d
    while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(G15) >= 0) {
      // 将G15左移补齐 与 d 异或运算
      d ^= (G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(G15)));
    }
    // data左移10位与 d 或运算的结果 与 G15_MASK 异或
    return ((data << 10) | d) ^ G15_MASK;
  },
  getBCHDigit: function (data) {
    var digit = 0;
    while (data != 0) {
      digit += 1;
      data >>>= 1;
    }
    return digit;
  },
  // 6、版本信息
  getBCHTypeNumber: function (data) {
    var d = data << 12;
    while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(G18) >= 0) {
      d ^= (G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(G18)));
    }
    return (data << 12) | d;
  },
  // 7、格式化数据
  qr8BitByte: function (data) {
    var _mode = QRMode.MODE_8BIT_BYTE;
    var _data = data;
    var _bytes = QRUtil.toUTF8Array(data);
    var _this = {};

    _this.getMode = function () {
      return _mode;
    };

    _this.getLength = function (buffer) {
      return _bytes.length;
    };

    _this.write = function (buffer) {
      for (var i = 0; i < _bytes.length; i += 1) {
        buffer.put(_bytes[i], 8);
      }
    };

    return _this;
  },
  toUTF8Array: function (str) {
    var utf8 = [];
    for (var i = 0; i < str.length; i++) {
      var charcode = str.charCodeAt(i);
      if (charcode < 0x80) utf8.push(charcode);
      else if (charcode < 0x800) {
        utf8.push(0xc0 | (charcode >> 6),
          0x80 | (charcode & 0x3f));
      }
      else if (charcode < 0xd800 || charcode >= 0xe000) {
        utf8.push(0xe0 | (charcode >> 12),
          0x80 | ((charcode >> 6) & 0x3f),
          0x80 | (charcode & 0x3f));
      }
      // surrogate pair
      else {
        i++;
        // UTF-16 encodes 0x10000-0x10FFFF by
        // subtracting 0x10000 and splitting the
        // 20 bits of 0x0-0xFFFFF into two halves
        charcode = 0x10000 + (((charcode & 0x3ff) << 10)
          | (str.charCodeAt(i) & 0x3ff));
        utf8.push(0xf0 | (charcode >> 18),
          0x80 | ((charcode >> 12) & 0x3f),
          0x80 | ((charcode >> 6) & 0x3f),
          0x80 | (charcode & 0x3f));
      }
    }
    return utf8;
  },
  qrBitBuffer: function () {
    var _buffer = [];
    var _length = 0;
    var _this = {};

    _this.getBuffer = function () {
      return _buffer;
    };

    _this.getAt = function (index) {
      var bufIndex = Math.floor(index / 8);
      return ((_buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1;
    };

    _this.put = function (num, length) {
      for (var i = 0; i < length; i += 1) {
        _this.putBit(((num >>> (length - i - 1)) & 1) == 1);
      }
    };

    _this.getLengthInBits = function () {
      return _length;
    };

    _this.putBit = function (bit) {
      var bufIndex = Math.floor(_length / 8);
      if (_buffer.length <= bufIndex) {
        _buffer.push(0);
      }
      if (bit) {
        _buffer[bufIndex] |= (0x80 >>> (_length % 8));
      }
      _length += 1;
    };

    return _this;
  },
  getLengthInBits: function (mode, type) {
    if (1 <= type && type < 10) {
      return 8
    } else if (type < 27) {
      return 16
    } else if (type < 41) {
      return 16
    } else {
      throw 'type:' + type;
    }
  },
  createBytes: function (buffer, rsBlocks) {
    var offset = 0;
    var maxDcCount = 0;
    var maxEcCount = 0;
    var dcdata = new Array(rsBlocks.length);
    var ecdata = new Array(rsBlocks.length);

    for (var r = 0; r < rsBlocks.length; r += 1) {
      var dcCount = rsBlocks[r].dataCount;
      var ecCount = rsBlocks[r].totalCount - dcCount;
      maxDcCount = Math.max(maxDcCount, dcCount);
      maxEcCount = Math.max(maxEcCount, ecCount);
      dcdata[r] = new Array(dcCount);
      for (var i = 0; i < dcdata[r].length; i += 1) {
        dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
      }
      offset += dcCount;
      var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
      var rawPoly = QRUtil.qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
      var modPoly = rawPoly.mod(rsPoly);
      ecdata[r] = new Array(rsPoly.getLength() - 1);
      for (var i = 0; i < ecdata[r].length; i += 1) {
        var modIndex = i + modPoly.getLength() - ecdata[r].length;
        ecdata[r][i] = (modIndex >= 0) ? modPoly.getAt(modIndex) : 0;
      }
    }

    var totalCodeCount = 0;
    for (var i = 0; i < rsBlocks.length; i += 1) {
      totalCodeCount += rsBlocks[i].totalCount;
    }

    var data = new Array(totalCodeCount);
    var index = 0;

    for (var i = 0; i < maxDcCount; i += 1) {
      for (var r = 0; r < rsBlocks.length; r += 1) {
        if (i < dcdata[r].length) {
          data[index] = dcdata[r][i];
          index += 1;
        }
      }
    }

    for (var i = 0; i < maxEcCount; i += 1) {
      for (var r = 0; r < rsBlocks.length; r += 1) {
        if (i < ecdata[r].length) {
          data[index] = ecdata[r][i];
          index += 1;
        }
      }
    }

    return data;
  },
  getErrorCorrectPolynomial: function (errorCorrectLength) {
    var a = QRUtil.qrPolynomial([1], 0);
    for (var i = 0; i < errorCorrectLength; i += 1) {
      a = a.multiply(QRUtil.qrPolynomial([1, QRUtil.QRMath.gexp(i)], 0));
    }
    return a;
  },
  qrPolynomial: function (num, shift) {
    if (typeof num.length == 'undefined') {
      throw num.length + '/' + shift;
    }

    var _num = function () {
      var offset = 0;
      while (offset < num.length && num[offset] == 0) {
        offset += 1;
      }
      var _num = new Array(num.length - offset + shift);
      for (var i = 0; i < num.length - offset; i += 1) {
        _num[i] = num[i + offset];
      }
      return _num;
    }();

    var _this = {};

    _this.getAt = function (index) {
      return _num[index];
    };

    _this.getLength = function () {
      return _num.length;
    };

    _this.multiply = function (e) {
      var num = new Array(_this.getLength() + e.getLength() - 1);
      for (var i = 0; i < _this.getLength(); i += 1) {
        for (var j = 0; j < e.getLength(); j += 1) {
          num[i + j] ^= QRUtil.QRMath.gexp(QRUtil.QRMath.glog(_this.getAt(i)) + QRUtil.QRMath.glog(e.getAt(j)));
        }
      }
      return QRUtil.qrPolynomial(num, 0);
    };

    _this.mod = function (e) {
      if (_this.getLength() - e.getLength() < 0) {
        return _this;
      }
      var ratio = QRUtil.QRMath.glog(_this.getAt(0)) - QRUtil.QRMath.glog(e.getAt(0));
      var num = new Array(_this.getLength());
      for (var i = 0; i < _this.getLength(); i += 1) {
        num[i] = _this.getAt(i);
      }
      for (var i = 0; i < e.getLength(); i += 1) {
        num[i] ^= QRUtil.QRMath.gexp(QRUtil.QRMath.glog(e.getAt(i)) + ratio);
      }
      // recursive call
      return QRUtil.qrPolynomial(num, 0).mod(e);
    };

    return _this;
  },
  // 纠错码
  getRSBlocks: function (typeNumber, errorCorrectionLevel) {
    var rsBlock = QRUtil.getRsBlockTable(typeNumber, errorCorrectionLevel);
    var length = rsBlock.length / 3;
    var list = [];

    for (var i = 0; i < length; i += 1) {
      var count = rsBlock[i * 3 + 0];
      var totalCount = rsBlock[i * 3 + 1];
      var dataCount = rsBlock[i * 3 + 2];
      for (var j = 0; j < count; j += 1) {
        list.push(QRUtil.qrRSBlock(totalCount, dataCount));
      }
    }
    return list;
  },
  getRsBlockTable: function (typeNumber, errorCorrectionLevel) {
    switch (errorCorrectionLevel) {
      case QRErrorCorrectionLevel.L:
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
      case QRErrorCorrectionLevel.M:
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
      case QRErrorCorrectionLevel.Q:
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
      case QRErrorCorrectionLevel.H:
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
      default:
        return undefined;
    }
  },
  qrRSBlock: function (totalCount, dataCount) {
    var _this = {};
    _this.totalCount = totalCount;
    _this.dataCount = dataCount;
    return _this;
  },
  // 8、蒙版
  getMaskFunction: function (maskPattern) {
    switch (maskPattern) {
      case QRMaskPattern.PATTERN000:
        return function (i, j) { return (i + j) % 2 == 0; };
      case QRMaskPattern.PATTERN001:
        return function (i, j) { return i % 2 == 0; };
      case QRMaskPattern.PATTERN010:
        return function (i, j) { return j % 3 == 0; };
      case QRMaskPattern.PATTERN011:
        return function (i, j) { return (i + j) % 3 == 0; };
      case QRMaskPattern.PATTERN100:
        return function (i, j) { return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; };
      case QRMaskPattern.PATTERN101:
        return function (i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
      case QRMaskPattern.PATTERN110:
        return function (i, j) { return ((i * j) % 2 + (i * j) % 3) % 2 == 0; };
      case QRMaskPattern.PATTERN111:
        return function (i, j) { return ((i * j) % 3 + (i + j) % 2) % 2 == 0; };
      default:
        throw 'bad maskPattern:' + maskPattern;
    }
  },
  QRMath: (function () {
    var EXP_TABLE = new Array(256);
    var LOG_TABLE = new Array(256);
    // initialize tables
    for (var i = 0; i < 8; i += 1) {
      EXP_TABLE[i] = 1 << i;
    }
    for (var i = 8; i < 256; i += 1) {
      EXP_TABLE[i] = EXP_TABLE[i - 4]
        ^ EXP_TABLE[i - 5]
        ^ EXP_TABLE[i - 6]
        ^ EXP_TABLE[i - 8];
    }
    for (var i = 0; i < 255; i += 1) {
      LOG_TABLE[EXP_TABLE[i]] = i;
    }

    var _this = {};
    _this.glog = function (n) {
      if (n < 1) {
        throw 'glog(' + n + ')';
      }
      return LOG_TABLE[n];
    };
    _this.gexp = function (n) {
      while (n < 0) {
        n += 255;
      }
      while (n >= 256) {
        n -= 255;
      }
      return EXP_TABLE[n];
    };
    return _this;
  }()),
}